#include "MappingEngine.h"
#include "DIVA_Manager.h"
#include "DIVA_InputManager.h"
//#include "class_mapping_gui.h"
#include "RegularPointer_new_lyvm.h"
#include "LyvmLayoutLibrary.h"

//#define _VMARRAY_ 
// #include "vmarray.h"

#include "xNodeType_VM.h"
#include "xNodeType_WOP.h"
#include "xNodeType_Math.h"
#include "xNodeType_StringComposer.h"
#include "xMappingNode.h"

#include <vector>

MappingEngine* MappingEngine::ME=NULL;


/// constructor
MappingEngine::MappingEngine(void){

	loadMDSfiles();
}

/// destructor
MappingEngine::~MappingEngine(void){
}

/// singleton method 
MappingEngine* MappingEngine::getInstance(){
	if (ME == NULL)
		ME = new MappingEngine();
	return ME;
}
void MappingEngine::add_wop_xmt_pair(WOP_Entry *en, xMappingTree* xmt) {
	auto itr = _wop_xmt_map.find(en) ;
	if (itr != _wop_xmt_map.end()) {
		return; // the wop and xmt pair is going to be inserted twice
	                   // how can that happen?
	}
	_wop_xmt_map.insert(std::make_pair(en, xmt));
	//_wop_xmt_map[en] = *xmt ;
}

xMappingTree * MappingEngine::get_xmt_by_entry(WOP_Entry *en) {
	auto itr = _wop_xmt_map.find(en) ;
	if (itr == _wop_xmt_map.end()) {
		return nullptr;  // why do you want to find an nonexist wop_entry ;
	}
	return itr->second ;
}

void MappingEngine::add_wop_vm_mapping_pair(WOP_Entry *en, DIVA_VM *vm) {
	std::vector<vm_wop_pair* >::iterator itr ;
	vm_wop_pair *p = new vm_wop_pair(vm,en);

	// we do not want to add it twice or more
	for (itr = mapping_table.begin(); itr != mapping_table.end(); itr++) {
		vm_wop_pair *q = (*itr);
		if (q->_we == en && q->_dvm == vm) return ;
	}
	mapping_table.push_back(p);
}


void MappingEngine::add_dummy_wop_link(WOP_Entry *en, DIVA_VM *vm) {
	

	vm_wop_pair *p = new vm_wop_pair(vm,en);
	std::vector<vm_wop_pair* >* _me_table = getDummyMapping_table();
	_me_table->push_back(p);

}
// called in command_agent when wop_entry is updated


void MappingEngine::trigger_valueupdate() {
	for ( auto itr = _wop_xmt_map.begin() ; itr != _wop_xmt_map.end(); itr++) {
		WOP_Entry* wop = itr->first ;
		xMappingTree * xmt = itr->second ;
		if (wop->isChanged())  // only when
		    xmt->update();
	}
}

std::vector<vm_wop_pair* >* MappingEngine::getMapping_table(){
	return &mapping_table;
}
std::vector<vm_wop_pair*> * MappingEngine::getDummyMapping_table() {
	return &dummy_mapping_table ;
}

DIVA_VM* MappingEngine::get_vm_by_entry(WOP_Entry* we){
	std::vector<vm_wop_pair*>* _table = getMapping_table();
	std::vector<vm_wop_pair*>::iterator itr;
	DIVA_VM* _dvm;
	WOP_Entry* _we;

	for (itr = _table->begin(); itr != _table->end(); itr++){
		_dvm = (*itr)->getVM();
		_we = (*itr)->get_WOP_Entry();
		if (we == _we) return _dvm;
	}
	return NULL;

}

WOP_Entry* MappingEngine::get_entry_by_vm(DIVA_VM* dvm){
	std::vector<vm_wop_pair*>* _table = getMapping_table();
	std::vector<vm_wop_pair*>::iterator itr;
	DIVA_VM* _dvm;
	WOP_Entry* _we;

	for (itr = _table->begin(); itr != _table->end(); itr++){
		_dvm = (*itr)->getVM();
		_we = (*itr)->get_WOP_Entry();
		if (dvm = _dvm) return _we;
	}
	return NULL;

}

DIVA_VM* MappingEngine::get_DummyVM_from_Entry(WOP_Entry* we){
	std::vector<vm_wop_pair*>* _table = getDummyMapping_table();
	std::vector<vm_wop_pair*>::iterator itr;
	DIVA_VM* _dvm;
	WOP_Entry* _we;

	for (itr = _table->begin(); itr != _table->end(); itr++){
		_dvm = (*itr)->getVM();
		_we = (*itr)->get_WOP_Entry();
		if (we == _we) return _dvm;
	}
	return NULL;

}

/*
void MappingEngine::createVM(MappingTree mt) {
	int i ;
	std::vector<MappingNode *>::iterator itr ;
	mt.compute_level();
	for (i=0; i<= mt.get_max_level(); i++) {
		std::vector<MappingNode*> nodes = mt.get_nodes_by_level(i) ;
		assert(nodes.size() > 0);
		for (itr=nodes.begin(); itr != nodes.end(); itr++) {
			if ((*itr)->get_type() == WVNODE) {
				WVnode * wvn = dynamic_cast<WVnode*> (*itr) ;
				MappingEngine::getInstance()->add_mapping_node(wvn); // add the mappign node to mapping engine
				alloc_VM_for_WVnode(wvn);  
			} else if ((*itr)->get_type() == VVNODE) {
				VVnode * vvn = dynamic_cast<VVnode*>(*itr);
				MappingEngine::getInstance()->add_mapping_node(vvn); // add hte mapping node to mapping engine
				alloc_VM_for_VVnode(vvn);
			} else {
				assert(false);
			}
		}
	}
	MappingNode * rootnode = mt.get_root();
	DIVA_VM *vm = rootnode->get_vm() ;
	assert(vm != NULL) ;
	addNewVM(vm);
 }

MappingNode * MappingEngine::get_mnode_from_VM(DIVA_VM *vm) {
	std::vector<MappingNode *>::iterator itr ;
	MappingNode * p ;
	for (itr = _all_mapping_nodes.begin(); itr != _all_mapping_nodes.end(); itr++) {
		p = (*itr);
		if (p->get_vm() == vm) return p ;
	}
	assert(false);
	return NULL ;
}
*/
void MappingEngine::doLayoutForNewVM() {
	if(LyvmLayoutLibrary::getInstance()->doesLayoutEnable()) return ;
	if (_newcreated.size() == 0) return ;
	DIVA_VM *vm = _newcreated[0] ;
	_newcreated.pop_front();

	RegularPointer_new_lyvm r ;
	if (vm->get_movable() == true) {
		r.doNewLayout(vm);
		r.setChange();
	}
}

/*-------------------------------- NEW MAPPING ENGINE WITH NEW xMAPPINGNODE -----------------------------------*/
void MappingEngine::xCreateMappedObject(xMappingTree* xmt) {
	vector<xMappingNode *>::iterator itr ;
	xmt->compute_level();  // compute the level 

	// level 0 should be all WOPs 
	// basically, we do not need to process anything.
	for (int i=1; i<= xmt->get_max_level(); i++) {
		vector<xMappingNode*> nodes = xmt->get_nodes_by_level(i);
		for (unsigned int j=0; j< nodes.size(); j++) {
			nodes[j]->getNodeTypePtr()->createMappedObject(); 
			// if a node is a VM node, it could create a VM object
			// The behaviors of the createMappedObject() depends on the type of node
		}
	}

	xmt->collect_wop_vm_pairs();
	for( auto it = xmt->get_wop_vm_pairs().begin(); it != xmt->get_wop_vm_pairs().end(); ++it ) {
		this->add_wop_vm_mapping_pair( it->first, it->second );
	}
}

void MappingEngine::addxMappingTree(xMappingTree * xmt) {
	
	/*以下由hyt所寫,改成vector理由, set需要複寫 greater than 以及 less than , xmt不用比大小 */
	bool repetition = false;
	for(auto it = _all_xMappingTree.begin() ; it != _all_xMappingTree.end() ; ++it){
		if(xmt == (*it)){
			repetition = true;
			break;
		}
	}
	if(!repetition){
		_all_xMappingTree.push_back(xmt);
	}
	/*以上由hyt所寫 */

	vector<xMappingNode*>::iterator itr ;
	vector<xMappingNode *> nodes = xmt->get_nodes();
	for (itr = nodes.begin() ;itr != nodes.end(); itr ++) {
		xMappingNode *p = (*itr);
		xNodeType *nt = p->getNodeTypePtr();
		_all_xMappingNodes.push_back((*itr));
		_all_node_xmt_map.insert(std::make_pair(p,xmt));
		//_all_node_xmt_map[p] = xmt ;
		if (nt->getxMappingTree()) { 
			// if the mapping node is a composite node, we add the nodes recursively 
			xMappingTree *childxmt = nt->getxMappingTree();
			addxMappingTree( childxmt );
			_xmt_child_parent_map.push_back( std::make_pair( childxmt, xmt ) );
		}
	}
	
}
xMappingTree * MappingEngine::getParentxMappingTree(xMappingNode *m) {
	auto itr = _all_node_xmt_map.find(m);
	if (itr == _all_node_xmt_map.end()) {
		return nullptr; // how come a mapping node does not belong to any xMappingTree
	}
	return itr->second ;	
}
/*given a mapping node, this method return the ROOT xMappingTree it belongs to */
xMappingTree * MappingEngine::getRootxMappingTree(xMappingNode *m) {
	auto itr = _all_node_xmt_map.find(m);
	if (itr == _all_node_xmt_map.end()) {
		return nullptr; // how come a mapping node does not belong to any xMappingTree
		// xMappingTree xmt ;
		// return xmt;
	}
	xMappingTree * currentxmt =  itr->second ;	
	// a xMappingTree x may belong to a xMappingTee y, and a xMappingTree y may belongs to another 
	// xMappingTree z. We need to recursively trace back to its root.

	do {
		
		xMappingTree * parent;
		bool found = false ;
		for (auto mitr = _xmt_child_parent_map.begin(); mitr != _xmt_child_parent_map.end(); mitr++) {
			if (mitr->first == currentxmt) {
				// xmt has a parent xMappingTree ;
				parent = mitr->second ;
				found = true ;
				break ;
			}
		}
		if (found) currentxmt = parent ;
		else	 
			return currentxmt ;
	} while (1);
}

xMappingNode * MappingEngine::xSearchNodeByVM(DIVA_VM *vm) {
	vector<xMappingNode*>::iterator itr ;
	for (itr = _all_xMappingNodes.begin() ; itr != _all_xMappingNodes.end(); itr++) {
		xMappingNode *p = (*itr);
		if (p->getType() != "vm") continue ;
		DIVA_VM * v = ((xNodeType_VM*)p->getNodeTypePtr())->getVM();
		if (v == vm) return p ;
	}
	return NULL ;
}
vector<DIVA_VM*> MappingEngine::xGetVMbyEntry(WOP_Entry* we){
	
	assert(false);  // this method has not been tested 
	vector<DIVA_VM*> ret ;
	/*
	vector<xMappingNode *>::iterator itr ;

	// first, trying to find the xMappingNode that carry wop_entry we
	bool found = false ;
	xMappingNode * foundxmn = NULL ;
	for (itr = _all_xMappingNodes.begin() ; itr != _all_xMappingNodes.end(); itr++) {
		xMappingNode *p = (*itr);
		xNodeType *nt = p->getNodeTypePtr();
		if (nt->getType() == "wop") {
			xNodeType_WOP *wnt= dynamic_cast<xNodeType_WOP*>(nt) ;
			assert(wnt != NULL) ;
			if (we == wnt->getWOP()) { 
				found = true ;
				foundxmn = p ;
				break ;
			}
		}
	}
	if (! found) return ret ; // return an empty vector

	// search up until the first VM mapping node is visited root is visited
	// OK, I think we have a problem here. 
	// in the complex mapping, a wop and a VM is not one-to-one mapping
	// for example, a wop may be used to position a VM but it has no one-to-one VM

	assert(false);
	vector<xMappingNode*> parents ;
	parents = xMappingTree::getParentNodes(foundxmn);
	while (parents.size() > 0) { }
*/
	return ret ;

}

WOP_Entry* MappingEngine::xGetEntrybyVM(DIVA_VM* dvm){
	assert(false);
	return NULL ;
}
DIVA_VM* MappingEngine::walk_xmt_for_vm(xMappingNode *start) {
	vector<xMappingNode*> parents =  xMappingTree::getParentNodes(start) ;

	// rule 1: when the wop entry is linked to more than one xMappingNode, we return NULL
	if (parents.size() > 1 ) {
		if (parents.size() != 2) return NULL ;
		xMappingNode *p1 = parents[0];  // we want check if the wop has two links to the same node
		xMappingNode *p2 = parents[1];  // if there are the same node, it is the case of link varname 
		if (p1 != p2)  return NULL ;
		vector<xMappingNode*> kids = xMappingTree::getChildNodes(parents[0]);

		if (kids.size() == 2) { // this is the case of connect varname and a required wop to the same node
			 	xNodeType *xt = parents[0]->getNodeTypePtr();
				xNodeType_VM *vmt = (xNodeType_VM*)xt ;
				assert(vmt != NULL);
				return vmt->getVM();
		}
		return NULL ;
    }

	// rule 2: the wop is linked to a vm node, but vm node has more than one kids 
	//         such as R,G,B case. In this case, we return NULL 

	vector<xMappingNode*> kids = xMappingTree::getChildNodes(parents[0]);
	if (parents.size() == 1 && parents[0]->getType() == "vm" && kids.size() != 1) return NULL ;


	// rule 3: the wop is linked to a vm node and is its unique link
	//         the vm and wop is paired, no doubt 
	if (parents.size() == 1 && parents[0]->getType() == "vm" && kids.size() == 1) {
		xNodeType *xt = parents[0]->getNodeTypePtr();
		xNodeType_VM *vmt = (xNodeType_VM*)xt ;
		assert(vmt != NULL);
		return vmt->getVM();
	}


	// all other cases, return NULL 
	return NULL ;

}

void MappingEngine::sweep_to_collect_vmentry_pairs(xMappingTree* xmt) {
	WOP_Entry* en ;
	xNodeType_WOP *wnt ;
	DIVA_VM * vm ;
	vector<xMappingNode *>::iterator itr ;
	xmt->compute_level();  // compute the level 

	vector<xMappingNode*> wop_nodes = xmt->get_nodes_by_level(0);
	if (wop_nodes.size() <= 1 ) return ;  // there is no need to sweep

	for (unsigned int j=0; j< wop_nodes.size(); j++) {
		wnt = (xNodeType_WOP*) wop_nodes[j]->getNodeTypePtr();assert(wnt != NULL);
		en = wnt->getWOP(); assert(en != NULL);
		vm = walk_xmt_for_vm (wop_nodes[j]); 
		
		// walk_xmt_for_vm() can return NULL if the mapping is ambiguous 
		// for example in R G B case, it will walk to a mapping node which has 3 links to 3 required ports
		// in this case, the vm is ambigous and walk_xmt_for_vm() shall return NULL
		if (vm != NULL)
			add_wop_vm_mapping_pair(en,vm);		
	}
}

void MappingEngine::addMDString(string vartype, string mdstring) {
	map<string, string>::iterator itr ;
	itr = _MDStable.find(vartype);
	if (itr == _MDStable.end()) {
		_MDStable.insert(pair<string,string>(vartype,mdstring));
		//_MDStable[vartype] = mdstring ;
		return ;
	}
	//assert(false); // in the future, we may want to allow the update of mdstring.
	return ;
}
void MappingEngine::removeMDString(string vartype) {
	map<string, string>::iterator itr ;
	itr = _MDStable.find(vartype);
	// Check if item exists
	if( itr != _MDStable.end() ) {
        _MDStable.erase(itr);
    }
    else {
        // Doesn't Exist
    }
}
string MappingEngine::searchMDString(string vartype) {

	map<string, string>::iterator itr ;
	itr = _MDStable.find(vartype);
	if (itr != _MDStable.end()) {
		return itr->second ;
	}
	return "" ;
}
	

void MappingEngine::saveToMDSfile(string filename, string vartype,string mdstring) {
	
	ofstream mdsfile(filename.c_str(), ios_base::out | ios_base::trunc);
	assert(!mdsfile.fail()); // if file open error, quit for now
	mdsfile << "MDS version 1.1" << endl ;
	mdsfile << "VARTYPE = " << vartype << endl ;
	mdsfile << "BEGIN" << endl ;
	mdsfile << mdstring << endl ;
	mdsfile << "END" << endl ;
	mdsfile.clear();
	mdsfile.close();	
}

pair<string,string> MappingEngine::readMDSfile(string filename) {
	pair<string, string> mds_pair ;
	string aline,vartype, mdsstr ;
	string str[10] ;


	ifstream mdsfile(filename.c_str(), ios_base::in) ;
	assert(mdsfile); // if file open error, quit for now

	getline(mdsfile, aline);
	assert(aline == "MDS version 1.1") ;
	mdsfile >> str[0] >> str[1] >> vartype ;
	assert(str[0] == "VARTYPE");
	assert(str[1] == "=" );
	
	mdsfile.ignore(1,'\n');
	getline(mdsfile, aline);
	assert(aline == "BEGIN");

	mdsstr = "" ;
	do {
		getline(mdsfile, aline);
		if (aline == "END") break ;
		mdsstr += aline + '\n' ;
	} while (1) ;

	mds_pair.first = vartype ;
	mds_pair.second = mdsstr ;
	return mds_pair ;
}

void MappingEngine::loadMDSfiles()
{
	string filename ;

	ifstream mdsfile("mds/DIVA.mds", ios_base::in) ;
	if (! mdsfile) return ;  // there is no DIVA.mds file, an empty _MDStable is created
	mdsfile >> filename ;
	assert(filename =="{");

	do {
		mdsfile >> filename ;
		if (mdsfile.good()) {
			if (filename == "}") { mdsfile.close() ; return ;}
			if (filename.find("//") == filename.npos) {
				pair<string, string> mds_pair = readMDSfile(filename) ;
				addMDString(mds_pair.first, mds_pair.second);
			}
		} else {
			assert(false);
		}
	} while (1) ;
}

void MappingEngine::reloadMDSfiles()
{
	_MDStable.clear();
	loadMDSfiles();
}

/*	
void MappingEngine::updateArithmeticNodes() { 

	vector<xMappingNode*> ::iterator itr ;
	vector<xMappingTree> done_list ;
	vector<xMappingTree>::iterator ditr ;

	
	for (itr = _all_arith_xMappingNodes.begin() ; itr != _all_arith_xMappingNodes.end(); itr++) {
		xMappingNode *p = (*itr);
		xMappingTree* xmt = _all_node_xmt_map[p] ;
		
		ditr = find(done_list.begin(), done_list.end(), xmt);
		if (ditr != done_list.end()) continue ; // this xMappingTree have been processed, skip to next one.


		xmt.compute_level();  
		for (int i=1; i<= xmt.get_max_level();i++) {
			vector<xMappingNode *> nodes = xmt.get_nodes_by_level(i);
			for (unsigned int j=0; j< nodes.size(); j++) {
				xMappingNode *n = nodes[j] ;
				if (n->getNodeTypePtr()->getType() == "math") {
					bool changed = ((xNodeType_Math*)n->getNodeTypePtr())->update() ;

					// if changed, we find all the parents that are VM node and trigger its value updates
					if (changed) {
						vector<xMappingNode*> parents = n->getParentNodes();
						while (parents.size() > 0) {
							assert(parents.size() == 1) ; 
							// in general case, parents can be more than one. The following code 
							// should be rewritten and modularized
							p = parents[0] ;
							if (p->getNodeTypePtr()->getType() == "vm") {
								DIVA_VM* vm = ((xNodeType_VM*)p->getNodeTypePtr())->getVM();
								vm->VMValueUpdated();
							}
							parents = p->getParentNodes();
						}
					}

				}
			}
		}
		done_list.push_back(xmt);
		
	}
	

}
*/